/**********************************************************************
 *                                                                    *
 * tgt - Tiny Graphics Toolbox                                        *
 *                                                                    *
 * Copyright (C) 2006-2008 Visualization and Computer Graphics Group, *
 * Department of Computer Science, University of Muenster, Germany.   *
 * <http://viscg.uni-muenster.de>                                     *
 *                                                                    *
 * This file is part of the tgt library. This library is free         *
 * software; you can redistribute it and/or modify it under the terms *
 * of the GNU Lesser General Public License version 2.1 as published  *
 * by the Free Software Foundation.                                   *
 *                                                                    *
 * This library is distributed in the hope that it will be useful,    *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of     *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the       *
 * GNU Lesser General Public License for more details.                *
 *                                                                    *
 * You should have received a copy of the GNU Lesser General Public   *
 * License in the file "LICENSE.txt" along with this library.         *
 * If not, see <http://www.gnu.org/licenses/>.                        *
 *                                                                    *
 **********************************************************************/

#include "tgt/navigation/navigation.h"
#include "tgt/quadric.h"
#include "tgt/toolkitfactory.h"
#include "tgt/guiapplication.h"
#include "tgt/painter.h"
#include "tgt/camera.h"

using namespace tgt;

GUIApplication* tgtApp;
GLCanvas* glCanvas;

class MyPainter : public Painter {
public:
    MyPainter(GLCanvas* canvas) : Painter(canvas) {};
    virtual void paint();
    virtual void init();
    virtual void sizeChanged(const ivec2& size);
    void obscureObject();
    void rings();
    void ring(ivec3 axis, float radius);
};

void MyPainter::paint() {

    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);

    // set Camera
    getCamera()->look();	
	Frustum frustum = getCamera()->getFrustum();
	frustum.update(getCamera());
	obscureObject();
    rings();
}

void MyPainter::init() {
    glEnable(GL_DEPTH_TEST);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    getCamera()->positionCamera(vec3(0.f,0.f,10.f), vec3(0.f,0.f,0.f), vec3(0.f,1.f,0.f));
}

void MyPainter::sizeChanged(const ivec2& size) {
    glViewport(0,0,size.x,size.y);
    getCamera()->setRatio((float) size.x/size.y);
}

void MyPainter::obscureObject() {		
    Sphere sp(1., 32, 16);
    Quad qu(2., 2., 2.);
    Cylinder cy(.5, .2, 5., 16, 3);

    glColor3d(.8,.8,.8);
    sp.render();

    glColor3d(1.,0.,0.);
    glPushMatrix();
      glRotated(90.,1.,0.,0.);
      cy.render();
    glPopMatrix();

    glColor3d(0.,1.,0.);
    glPushMatrix();
      glRotated(90.,0.,1.,0.);
      cy.render();
    glPopMatrix();

    glColor3d(0.,0.,1.);
    glPushMatrix();
      glRotated(90.,0.,0.,1.);
      cy.render();
    glPopMatrix();

    glColor3d(1.,1.,0.);
    glPushMatrix();
      glTranslated(-1., 1., -1.);
      qu.render();
    glPopMatrix();
}

void MyPainter::rings() {
    ring(ivec3(1, 0, 0), 20.f);
    ring(ivec3(0, 1, 0), 20.f);
    ring(ivec3(0, 0, 1), 20.f);
}

void MyPainter::ring(ivec3 axis, float radius) {
    // find a vector orthogonal to axis
    vec3 trans = axis;
    if (axis.x != 0) {
        trans.x = (axis.y*trans.y+axis.z*trans.z) / axis.x;
        if (trans.x == 0.f)
            trans.y = 1.f;
    } else if (axis.y != 0) {
        trans.y = (axis.x*trans.x+axis.z*trans.z) / axis.y;
        if (trans.y == 0.f)
            trans.z = 1.f;
    } else if (axis.z != 0) {
        trans.z = (axis.x*trans.x+axis.y*trans.y) / axis.z;
        if (trans.z == 0.f)
            trans.x = 1.f;
    } else return;
    trans = radius * normalize(trans);

    Sphere sp(.2, 16, 8);
    glColor3d(1.,1.,1.);

    for (int i = 0; i < 36; ++i) {
        glPushMatrix();
        glRotatef(i*10.f, axis.x, axis.y, axis.z);
        glTranslatef(trans.x, trans.y, trans.z);
        sp.render();
        glPopMatrix();
    }
}

class MyNavigation : public Navigation {
private:
    ivec2 lastMouseCoord_;
public:
    MyNavigation(GLCanvas* canvas) : Navigation(canvas) {};
    virtual void mousePressEvent(MouseEvent* e);
    virtual void mouseMoveEvent(MouseEvent* e);
    virtual void wheelEvent(MouseEvent* e);
    virtual void keyEvent(KeyEvent *e);
};

void MyNavigation::mousePressEvent(MouseEvent* e) {
    lastMouseCoord_ = e->coord();
}

void MyNavigation::mouseMoveEvent(MouseEvent* e) {
    if (e->button() & MouseEvent::MOUSE_BUTTON_LEFT) {
        rotateViewHorz(-(e->coord().x-lastMouseCoord_.x)*PI/1000);
        rotateViewVert((e->coord().y-lastMouseCoord_.y)*PI/1000);
        glCanvas->update();
    }
    lastMouseCoord_ = e->coord();
}

void MyNavigation::wheelEvent(MouseEvent* e) {
    if (e->button() & MouseEvent::MOUSE_WHEEL_DOWN) {
        rollCameraHorz( 2*PI/16.);
    } else if (e->button() & MouseEvent::MOUSE_WHEEL_UP) {
        rollCameraHorz(-2*PI/16.);
    }
    glCanvas->update();
}

void MyNavigation::keyEvent(KeyEvent *e) {
    if (e->pressed() == false) {
        // only on key release
        switch (e->keyCode()) {
        case KeyEvent::K_HOME:
            getCanvas()->getPainter()->init();
            break;
        case KeyEvent::K_LEFT:
            moveCameraLeft(.2f);
            break;
        case KeyEvent::K_RIGHT:
            moveCameraRight(.2f);
            break;
        case KeyEvent::K_UP:
            moveCameraUp(.2f);
            break;
        case KeyEvent::K_DOWN:
            moveCameraDown(.2f);
            break;
        case KeyEvent::K_PAGEUP:
            moveCameraForward(.2f);
            break;
        case KeyEvent::K_PAGEDOWN:
            moveCameraBackward(.2f);
            break;
        case KeyEvent::K_ESCAPE:
            tgtApp->quit();
            break;
        default: ;
        }
        glCanvas->update();
    }
}

int main(int argc, char** argv) {

    std::cout
        << "tgt Sample Program: navigation" << std::endl
        << std::endl
        << "Demonstrates implementation of custom made navigation upon keyboard and mouse events." << std::endl
        << std::endl;
    
    std::cout
        << "Usage:" << std::endl
        << "Left-/Right-/Up-/Down-Arrow :  move camera left/right/up/down" << std::endl
        << "Page-Up/Page-Down           :  move camera forward/backward" << std::endl
        << "drag mouse                  :  look around (turn camera)" << std::endl
        << "mouse wheel                 :  roll camera" << std::endl
        << "Home                        :  reset camera" << std::endl
        << "Esc                         :  exit" << std::endl
        << std::endl;
    
    tgtApp = ToolkitFactory::createApplication(argc, argv);

    glCanvas = ToolkitFactory::createCanvas("tgt Example: Navigation");
    tgtApp->addCanvas(glCanvas);
    tgtApp->init();

    Camera camera;
    glCanvas->setCamera(&camera);

    MyPainter painter(glCanvas);
    glCanvas->setPainter(&painter);

    MyNavigation navigation(glCanvas);
    glCanvas->getEventHandler()->addListenerToFront(&navigation);

    tgtApp->run();

    delete tgtApp;
    delete glCanvas;

    return 0;
}
